package net.ewon.config;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

public class NaviConfiguration {

	private static final String GLOBAL = "global";
	private static final String GROUP = "group";
	private static final String PROJECT = "project";
	private static final String PERSONAL = "personal";

	private static final String SERVICE_HOME = "service.home";

	private static final Logger logger = Logger
			.getLogger(NaviConfiguration.class);

	private PropertiesConfiguration propConfig;

	private Properties properties;

	private Domain domain = Domain.test;

	private NaviConfiguration(String configHome) throws ConfigurationException {

		try {
			String env = System.getProperty("domain", Domain.test.name());
			domain = Domain.valueOf(env);
		} catch (IllegalArgumentException e) {
			logger.warn("The env is illegal[Legal Value: dev, test, prod]. We fall back to test environment.");
		}
		logger.info("The Config Home is: " + configHome + ", environment is:"
				+ domain);

		propConfig = new NaviPropertiesConfiguration(domain);
		properties = new Properties();

		List<File> files = new ArrayList<File>();

		addFiles(files, configHome, GLOBAL);
		addFiles(files, configHome, GROUP);
		addFiles(files, configHome, PROJECT);
		addFiles(files, configHome, PERSONAL);

		for (File file : files) {
			propConfig.load(file);
		}
		loadProperties();
	}

	public static NaviConfiguration createInstance()
			throws ConfigurationException {
		String serviceHome = System.getProperty(SERVICE_HOME);
		String configHome = "";
		if (serviceHome != null) {
			configHome = StringUtils.removeEnd(
					StringUtils.removeEnd(serviceHome, "/"), "\\\\")
					+ File.separator + "config";
		} else {
			configHome = NaviConfiguration.class.getClassLoader()
					.getResource("").getFile();
		}

		return createInstance(configHome);
	}

	public static NaviConfiguration createInstance(String configHome)
			throws ConfigurationException {
		NaviConfiguration naviConfig = new NaviConfiguration(configHome);

		return naviConfig;
	}

	public Properties getProperties() {
		return properties;
	}

	public Object getProperty(String key) {
		return propConfig.getProperty(key);
	}

	public String getString(String key) {
		return propConfig.getString(key);
	}

	public String getString(String key, String defaultValue) {
		return propConfig.getString(key, defaultValue);
	}

	public int getInt(String key) {
		return propConfig.getInt(key);
	}

	public int getInt(String key, int defaultValue) {
		return propConfig.getInt(key, defaultValue);
	}

	public boolean getBoolean(String key) {
		return propConfig.getBoolean(key);
	}

	public boolean getBoolean(String key, boolean defaultValue) {
		return propConfig.getBoolean(key, defaultValue);
	}

	public long getLong(String key) {
		return propConfig.getLong(key);
	}

	public long getLong(String key, long defaultValue) {
		return propConfig.getLong(key, defaultValue);
	}

	public float getFloat(String key) {
		return propConfig.getFloat(key);
	}

	public float getFloat(String key, float defaultValue) {
		return propConfig.getFloat(key, defaultValue);
	}

	public double getDouble(String key) {
		return propConfig.getDouble(key);
	}

	public double getDouble(String key, double defaultValue) {
		return propConfig.getDouble(key, defaultValue);
	}

	private void loadProperties() {
		Iterator it = propConfig.getKeys();
		while (it.hasNext()) {
			String key = (String) it.next();
			this.properties.put(key, propConfig.getProperty(key));
		}
	}

	private static void addFiles(List<File> files, String configPath,
			String type) {
		File configDir = new File(configPath + File.separator + type);
		System.out.println("configDir=" + configDir.getAbsolutePath());
		if (!configDir.exists() || !configDir.isDirectory()) {
			return;
		}

		logger.info("Load the configuration files from the directory:"
				+ new File(configPath + File.separator + type)
						.getAbsolutePath());

		Collection<File> typeFiles = FileUtils.listFiles(new File(configPath
				+ File.separator + type), new String[] { "cfg" }, false);
		files.addAll(typeFiles);
	}

}

class NaviPropertiesConfiguration extends PropertiesConfiguration {

	private Domain domain;

	public NaviPropertiesConfiguration(Domain domain) {
		this.domain = domain;
	}

	// FIXME If *.config is in global, the *.conf in project won't override it.
	protected void addPropertyDirect(String key, Object value) {
		if (isForEnvironment(key)) {
			String truncatedKey = getTruncatedKey(key);

			if (getProperty(truncatedKey) == null || !isSharedKey(key)) {
				clearPropertyDirect(truncatedKey);
				super.addPropertyDirect(truncatedKey, value);
			}
		}
	}

	private boolean isForEnvironment(String key) {
		return key.startsWith("*.") || key.startsWith(domain.toString() + ".");
	}

	private boolean isSharedKey(String key) {
		return key.startsWith("*.");
	}

	private String getTruncatedKey(String key) {
		int index = key.indexOf(".");
		if (index > 0) {
			return key.substring(index + 1);
		}
		return key;
	}

}
